package com.logic.modular.game.battle.process;

import com.comment.exception.ServiceException;
import com.logic.comment.util.RedisUtils;
import com.logic.modular.game.battle.info.roundInfo.dto.PvpBattleStatisticsPlayerInfo;
import com.logic.modular.game.battle.panel.Battle;
import com.logic.modular.game.battle.panel.attribute.domain.PlayerHeroAttribute;
import com.logic.modular.game.battle.panel.skill.PanelInfoService;
import com.logic.modular.game.battle.info.roundInfo.PvpBattleStatisticsInfoServiceImpl;
import com.logic.modular.game.battle.panel.attribute.PlayerHeroAttributeServiceImpl;
import com.logic.modular.game.battle.process.battleStage.PreparationStageOfRoundService;
import com.logic.modular.game.battle.process.battleStage.actionPhase.actionPhase1.PreparationBeforeActionService;
import com.logic.modular.game.battle.process.battleStage.actionPhase.actionPhase2.ActionStageService;
import com.logic.modular.game.battle.process.battleStage.actionPhase.actionPhase2.item.ReleaseSkillService;

import com.logic.modular.game.battle.process.domain.*;
import com.comment.pojo.domain.logic.domain.PlayerHero;
import com.comment.pojo.domain.logic.domain.PvpRanks;
import com.logic.modular.game.battle.info.roundInfo.domain.PvpBattleStatisticsInfo;
import com.logic.modular.game.player.info.service.PlayerStatusServiceImpl;
import com.logic.modular.game.PvpCity;
import com.logic.modular.game.player.match.service.PvpCityServiceImpl;
import com.logic.modular.game.player.match.service.PvpNpcRanksServiceImpl;
import com.logic.modular.game.player.match.service.PvpRanksServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.stream.Collectors;

import static com.logic.modular.game.battle.info.roundInfo.dto.messageDto.PvpMessageDto.obtainWinnerPlayerId;

@Validated
@Slf4j
@Service
@RequiredArgsConstructor
public class PlayerPvpServiceImpl {
    @Resource
    private PvpRanksServiceImpl pvpRanksService;
    private final PvpCityServiceImpl pvpCityServiceImpl;
    private final PlayerStatusServiceImpl playerMatchServiceImpl;
    private final ReleaseSkillService releaseSkillService;
    private final PlayerHeroAttributeServiceImpl attributeService;
    private final PvpBattleStatisticsInfoServiceImpl pvpBattleStatisticsInfoServiceImpl;
    private final PvpNpcRanksServiceImpl pvpNpcRanksServiceImpl;
    private final PreparationStageOfRoundService preparationStageOfRound;
    private final PanelInfoService panelInfoService;
    private final PreparationBeforeActionService preparationBeforeActionService;
    private final ActionStageService actionService;
    private final PlayerHeroAttributeServiceImpl playerHeroAttributeServiceImpl;
    private final RedisUtils redisUtils;
    @Autowired
    private PvpRanksServiceImpl pvpRanksServiceImpl;

    /**
     * 攻打NPC 城池
     */
    @Transactional
    public void attackNpcCity(
            @NotNull(message = "玩家id不能为空") Long attackPlayerId,
            @NotNull(message = "队伍编号不能为空") Integer attackRankNo,
            @NotNull(message = "攻打的城池不能为空") Long defenseCityId
    ) {
        Long currentMatchId = playerMatchServiceImpl.currentMatchId(attackPlayerId);
        // 获取队伍
        PvpRanks attackRanks = pvpRanksService.select(attackPlayerId, attackRankNo);
        if (attackRanks == null) {
            throw new ServiceException("攻击方队伍未出战");
        }


        // 防守城池
        PvpCity npcPvpCity = pvpCityServiceImpl.getCache(currentMatchId, defenseCityId);
        if (npcPvpCity == null) {
            throw new ServiceException("没有找到npc城池");
        } else if (npcPvpCity.getStatus() == 1) {
            throw new ServiceException("当前城池已被占领");
        } else if (npcPvpCity.getStatus() == 2) {
            throw new ServiceException("当前城池正在被攻击");
        }



        // npc队伍
        List<PlayerHero> defenseSide = pvpNpcRanksServiceImpl.getNpcCityHero(
                currentMatchId, defenseCityId
        );
        Integer status = attackRanks.getStatus();
        if (status == 2) {
            throw new ServiceException("队伍战斗中");
        } else if (status == 3) {
            throw new ServiceException("出征中");
        }

        String uuid = UUID.randomUUID().toString();
        List<PvpBattleStatisticsInfo> pvpBattleStatisticsInfoList = null;

        pvpBattleStatisticsInfoList = attackTheCityWalls(
                uuid,
                currentMatchId,
                attackPlayerId,
                attackRankNo,
                0L,
                1,
                defenseSide,
                defenseCityId
        );
        Long winnerPlayerId = obtainWinnerPlayerId(pvpBattleStatisticsInfoList);
        tryRanksOccupyTheCity(attackRankNo, npcPvpCity, winnerPlayerId, attackPlayerId, currentMatchId);
    }

    @Transactional
    public void attackCity(
            @NotNull(message = "玩家id不能为空") Long attackPlayerId,
            @NotNull(message = "队伍编号不能为空") Integer attackRanksNo,
            @NotNull(message = "攻打的城池不能为空") Long defenseCityId
    ) {
        Long currentMatchId = playerMatchServiceImpl.currentMatchId(attackPlayerId);
        // 获取队伍
        PvpRanks attackRanks = pvpRanksService.select(attackPlayerId, attackRanksNo);
        // 防守城池
        PvpCity targetCity = pvpCityServiceImpl.getCache(currentMatchId, defenseCityId);
        if (targetCity == null) {
            throw new ServiceException("没有找到城池");
        } else if (targetCity.getStatus() == 2) {
            throw new ServiceException("当前城池正在被攻击");
        }

        Long defPlayerId = targetCity.getOwnerUid();
        if (Objects.equals(defPlayerId, attackPlayerId)) {
            log.info("不能攻打自己的城池");
            throw new ServiceException("不能攻打自己的城池");
        }
        if (defPlayerId == null || defPlayerId == 0) {
            attackNpcCity(attackPlayerId, attackRanksNo, defenseCityId);
            return;
        }


        List<PlayerHero> attackSide = pvpRanksService.obtainSurvivingHero(
                attackPlayerId, attackRanksNo).stream()
                .peek(item -> item.setRankCamp(1))
                .collect(Collectors.toList());
        if (attackSide.isEmpty()) {
            log.info("攻击方武将没有兵力");
            throw new ServiceException("攻击方武将没有兵力");
        }

        // 防守队伍
        List<Integer> defRanksNoList = pvpRanksService.obtainCityRanksNoList(
                targetCity.getCoreCityId(), defPlayerId
        );


        attackRanks.setStatus(2);
        pvpRanksService.updateById(attackRanks);
        Map<Integer, List<PvpBattleStatisticsInfo>> map = new HashMap<>();
        String uuid = UUID.randomUUID().toString();


        if (defRanksNoList.isEmpty()) {//攻打空城
            attributeService.init(uuid, attackSide);
            PvpBattleStatisticsInfo init = pvpBattleStatisticsInfoServiceImpl.init(
                    uuid,
                    currentMatchId,
                    attackPlayerId,
                    defPlayerId,
                    defenseCityId
            );
            init.setWinPlayerId(attackPlayerId);
            pvpBattleStatisticsInfoServiceImpl.saveCache(init);
            tryRanksOccupyTheCity(attackRanksNo, targetCity, attackPlayerId, attackPlayerId, currentMatchId);
            return;
        }

        for (Integer ranksNo : defRanksNoList) {
            List<PlayerHero> defenseSide = pvpRanksServiceImpl.obtainCityRanksHeroByNo(defenseCityId, defPlayerId, Math.toIntExact(ranksNo));
            if (defenseSide.isEmpty()){
                continue;
            }
            List<PvpBattleStatisticsInfo> pvpBattleStatisticsInfos = attackTheCityWalls(
                    uuid,
                    currentMatchId,
                    attackPlayerId,
                    attackRanksNo,
                    defPlayerId,
                    ranksNo,
                    defenseSide,
                    defenseCityId);
            map.put(ranksNo, pvpBattleStatisticsInfos);

            PvpBattleStatisticsInfo pvpBattleStatisticsInfo = pvpBattleStatisticsInfos.stream()
                    .max(Comparator.comparingLong(PvpBattleStatisticsInfo::getBattleId))
                    .get();
            Long winPlayerId = pvpBattleStatisticsInfo.getWinPlayerId();
            if (winPlayerId != 0 && !winPlayerId.equals(attackPlayerId)) {
                log.info("winPlayerId:{}", winPlayerId);
                break;
            }
        }

        List<PvpBattleStatisticsInfo> collect = map.values().stream()
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
        Long winnerPlayerId = obtainWinnerPlayerId(collect);
        tryRanksOccupyTheCity(attackRanksNo, targetCity, winnerPlayerId, attackPlayerId, currentMatchId);
    }


    /**
     * 队伍对战
     */
    @Transactional
    public List<PvpBattleStatisticsInfo> attackTheCityWalls(
            @NotNull(message = "战斗UUID") String battleUUID,
            @NotNull(message = "当前对战id的对局id不能为空") Long currentMatchId,
            @NotNull(message = "攻击方玩家id不能为空") Long attackPlayerId,
            @NotNull(message = "进攻方队伍编号不能为空") Integer attackRankNo,
            @NotNull(message = "防守方玩家id不能为空") Long defPlayerId,
            @NotNull(message = "防守方队伍编号不能为空") Integer defRanksNO,
            @NotNull(message = "防守方武将不能为空") List<PlayerHero> defenseSide,
            @NotNull(message = "目标城池") Long targetCity
    ) {
        List<PvpBattleStatisticsInfo> backList = null;

        List<PlayerHero> attackSide = pvpRanksService.obtainSurvivingHero(attackPlayerId, attackRankNo).stream()
                .peek(item -> item.setRankCamp(1))
                .collect(Collectors.toList());
        if (attackSide.isEmpty()) {
            throw new ServiceException("攻击方武将没有兵力");
        }

        Battle battle = new Battle();
        battle.setBattleUUID(battleUUID);
        battle.setMatchId(currentMatchId);
        battle.save();

        List<PlayerHero> actionList = new ArrayList<>();
        actionList.addAll(attackSide);
        actionList.addAll(defenseSide);

        attributeService.init(battleUUID, actionList);

        backList = new ArrayList<>();
        PvpBattleStatisticsInfo pvpBattleStatisticsInfo = null;


        int battleCount = 1;
        Long battleId = 0L;
        int roundCount = 0;
        while (true) {
            if (roundCount == 0) {
                pvpBattleStatisticsInfo = pvpBattleStatisticsInfoServiceImpl.init(
                        battleUUID,
                        currentMatchId,
                        attackPlayerId,
                        defPlayerId,
                        targetCity
                );
                battleId = pvpBattleStatisticsInfo.getBattleId();

                panelInfoService.init(battleId, actionList);

                battle = Battle.get(battleUUID);
                battle.setBattleId(battleId);
                battle.logBattle();
                battle.save();
                log.info("=================={}回合,准备阶段", roundCount);

                BattleInfo battleInfo = new BattleInfo();
                battleInfo.setBattleId(battleId);
                battleInfo.setPlayerId1(attackPlayerId);
                battleInfo.setPlayerId0(defPlayerId);


                PreparationStageInfo preparationStageInfo = new PreparationStageInfo();
                log.info("==================被动战法");
                List<PlayerHeroAttribute> init = attributeService.initBattlePanel(battleUUID, battleCount);
                init = init.stream().filter(item -> item.getCurrentForce() > 0).sorted(Comparator.comparing(PlayerHeroAttribute::getSpeed)).collect(Collectors.toList());
                for (PlayerHeroAttribute actWJ : init) {
                    log.info("玩家{}武将{}({})兵力:{}", actWJ.getUid(), actWJ.getName(), actWJ.getId(), actWJ.getCurrentForce());
                    List<SkillReleaseInfo> list = releaseSkillService.heroReleaseSkills(battleUUID, actWJ, 2, init);
                    preparationStageInfo.skill2ReleaseList.addAll(list);
                }

                log.info("==================指挥战法");
                for (PlayerHeroAttribute actWJ : init) {
                    log.info("玩家{}武将{}( {})兵力:{}", actWJ.getUid(), actWJ.getName(), actWJ.getId(), actWJ.getCurrentForce());
                    List<SkillReleaseInfo> list = releaseSkillService.heroReleaseSkills(battleUUID, actWJ, 3, init);
                    preparationStageInfo.skill3ReleaseList.addAll(list);
                }
                battleInfo.setPreparationStageInfo(preparationStageInfo);
                battleInfo.saveCache();
                battleCount++;
            }
            roundCount++;
            battle = Battle.get(battleUUID);
            battle.addRound();

            Map<Integer, List<PlayerHeroAttribute>> sidesSurvivalHero =
                    playerHeroAttributeServiceImpl.obtainBothSidesSurvivalHeroMap(battleUUID);
            ///////////////////////////////////////////////////////////////////////////////////////////// END: 结束攻城
            if (actionList.isEmpty() || sidesSurvivalHero.size() == 1 || sidesSurvivalHero.isEmpty()) {
                log.info("END: 结束攻城");

                Long winnerId = null;
                boolean defendingSideWins = sidesSurvivalHero.containsKey(0);
                if (defendingSideWins) {
                    winnerId = defPlayerId;
                }
                boolean attackSideWins = sidesSurvivalHero.containsKey(1);
                if (attackSideWins) {
                    winnerId = attackPlayerId;
                }

                pvpBattleStatisticsInfo = endTheRound(battleUUID, winnerId, roundCount);
                backList.add(pvpBattleStatisticsInfo);

                redisUtils.delete(attackPlayerId + "-" + attackRankNo);
                break;
            }
            ///////////////////////////////////////////////////////////////////////////////////////////// END: 进入下一场
            if (sidesSurvivalHero.size() == 2 && roundCount == 9) {
                log.info("END: 进入下一场");
                pvpBattleStatisticsInfo = endTheRound(battleUUID, null, roundCount);
                backList.add(pvpBattleStatisticsInfo);

                // 结算丢失的兵力
                attributeService.endRefreshTroops(battleUUID);
                roundCount = 0;

                battle.setVenue(battle.getVenue() + 1);
                continue;
            }
            //////////////////////////////////////////////////////////////////////////////////////////////////// 开始战斗
            int orderOfAction = 0;
            log.info("=================={}回合", roundCount);
            battle = Battle.get(battleUUID);
            battle.setRound(roundCount);
            battle.save();


            BattleRoundInfo apply = preparationStageOfRound.apply(battleUUID);
            Set<Long> deathLabel = new HashSet<>();
            for (PlayerHeroAttribute actWJ : preparationStageOfRound.obtainActHeroList(battleUUID)) {
                orderOfAction++;
                Long actWJId = actWJ.getId();

                Map<Integer, List<PlayerHeroAttribute>> sidesSurvivalHeroMap =
                        playerHeroAttributeServiceImpl.obtainBothSidesSurvivalHeroMap(battleUUID);
                if (sidesSurvivalHeroMap.size() == 1) {
                    break;
                }
                if (deathLabel.contains(actWJId)) {
                    continue;// 死亡通过
                }
                //////////////////////////////////////////////////////////////////////////////////////////////////
                RoundActionInfo roundActionInfo = new RoundActionInfo();
                roundActionInfo.setOrderOfAction(orderOfAction);
                roundActionInfo.setActionPlayerHeroId(actWJId);

                roundActionInfo.setPreActionInfo(preparationBeforeActionService.apply(battleUUID, actWJ));
                roundActionInfo.setActionInfo(actionService.actionApply(battleUUID, actWJ));
                /////////////////////////////////////////////////////////////////////////////////////////////////
                deathLabel = playerHeroAttributeServiceImpl.getDeathLabel(battleUUID);
                log.info("战斗死亡{}", deathLabel);
                /////////////////////////////////////////////////////////////////////////////////////////////////
                apply.roundActionInfoMap.put(orderOfAction, roundActionInfo);
                BattleInfo battleInfo = BattleInfo.selectCache(battleId);
                battleInfo.battleRound.put(battle.getRound(), apply);
                battleInfo.saveCache();
            }
        }

        return backList;
    }

    private PvpBattleStatisticsInfo endTheRound(
            String battleUUID,
            Long winnerId,
            int roundCount
    ) {
        Battle battle = Battle.get(battleUUID);
        Long battleId = battle.getBattleId();

        attributeService.endRefreshTroops(battleUUID);
        PvpBattleStatisticsInfo byId = pvpBattleStatisticsInfoServiceImpl.getById(battleId);
        byId.setWinPlayerId(winnerId);
        byId.setEndTime(new Date());
        byId.setRoundCount(roundCount);

        Map<Integer, List<PlayerHeroAttribute>> collect = playerHeroAttributeServiceImpl.listCache(battleUUID)
                .stream().collect(Collectors.groupingBy(PlayerHeroAttribute::getRankCamp));

        // 处理攻击方
        List<PlayerHeroAttribute> attCamp = collect.get(1);
        long sum2 = 0L;
        for (PlayerHeroAttribute item : attCamp) {
            PvpBattleStatisticsPlayerInfo.HeroInfo heroInfo = byId.attackPlayerInfo.heroInfoMap.get(item.getId());
            long afterForce = item.getCurrentForce() == null ? 0 : item.getCurrentForce();
            if (heroInfo != null) {
                sum2 += afterForce;
                heroInfo.setForceAfter(afterForce);
            }
        }
        byId.attackPlayerInfo.setTotalForceAfter(sum2);
        pvpBattleStatisticsInfoServiceImpl.updateById(byId);

        // 处理防守方
        List<PlayerHeroAttribute> defendCamp = collect.get(0);
        long sum1 = 0L;
        if (defendCamp!=null) {
            for (PlayerHeroAttribute item : defendCamp) {
                PvpBattleStatisticsPlayerInfo.HeroInfo heroInfo = byId.defendPlayerInfo.heroInfoMap.get(item.getId());
                long afterForce = item.getCurrentForce() == null ? 0 : item.getCurrentForce();
                if (heroInfo != null) {
                    sum1 += afterForce;
                    heroInfo.setForceAfter(afterForce);
                }
            }
            byId.defendPlayerInfo.setTotalForceAfter(sum1);
        }
        pvpBattleStatisticsInfoServiceImpl.updateById(byId);

        return byId;
    }

    /**
     * 攻击方尝试占领城池
     */
    private void tryRanksOccupyTheCity(
            @NotNull(message = "攻击方队伍编号不能为空") Integer attackRankNo,
            @NotNull(message = "占领的城池不能为空") PvpCity pvpCity,
            @NotNull(message = "占领者id不能为空") Long winnerPlayerId,
            @NotNull(message = "攻击者id不能为空") Long attackPlayerId,
            @NotNull(message = "当前对战id的对局id不能为空") Long currentMatchId
    ) {
        Long cityCoreId = pvpCity.getCoreCityId();
        Long defPlayerId = pvpCity.getOwnerUid();
        if (winnerPlayerId == null || winnerPlayerId == 0) {
            return;
        }
        pvpCity.setStatus(1);
        pvpCityServiceImpl.savaCache(pvpCity);

        if (winnerPlayerId.equals(attackPlayerId)) { // 进攻成功
            log.info("{}进攻成功", attackPlayerId);
            // 攻击方获胜
            PvpRanks winnerRanks = pvpRanksService.select(winnerPlayerId, attackRankNo);
            winnerRanks.setCityId(cityCoreId);
            winnerRanks.setStatus(1);
            pvpRanksService.updateById(winnerRanks);
            pvpCityServiceImpl.playerOccupyTheCity(
                    currentMatchId, pvpCity.getOwnerUid(), pvpCity.getCoreCityId(),winnerPlayerId
            );

            // 防守方丢失城池 队伍数据处理
            pvpRanksService.obtainPvpRanKs(defPlayerId).stream()
                    .filter(item -> Objects.equals(item.getCityId(), cityCoreId))
                    .forEach(item -> {
                        item.clear();
                        pvpRanksService.updateById(item);
                    });
        } else if (winnerPlayerId.equals(defPlayerId)) { // 进攻失败
            PvpRanks attackRank = pvpRanksService.select(attackPlayerId, attackRankNo);
            attackRank.setStatus(1);
            pvpRanksService.updateById(attackRank);

            pvpRanksService.obtainPvpRanKs(defPlayerId).stream()
                    .filter(item -> Objects.equals(item.getCityId(), cityCoreId))
                    .filter(item -> Objects.equals(item.getStatus(), 2))
                    .forEach(item -> {
                        item.setStatus(1);
                        pvpRanksService.updateById(item);
                    });
        }
    }
}

